perm filename PONYSY.SAI[PNY,SYS]7 blob
sn#126716 filedate 1974-10-30 generic text, type C, neo UTF8
COMMENT ⊗ VALID 00007 PAGES
C REC PAGE DESCRIPTION
C00001 00001
C00002 00002 BEGIN "PONY" COMMENT
C00005 00003 COMMENT Useful Sail macros
C00010 00004 ! Initialization
C00015 00005 ! SCANNING PROCEDURES
C00020 00006 ! OUTPUT PROCEDURES
C00025 00007 ! DO IT
C00029 ENDMK
C⊗;
BEGIN "PONY" COMMENT
Prancing Pony Point-of-sale Terminal
If you are a "local" user of our system you may charge food on this
terminal. Example: if your programmer initials are "LEO" and you
want to buy a cup of coffee, type
LEO C<return>
To buy two bagels, a donut, a 15 cent vending machine item, a 35 cent
vending machine item, and $1 in change, type
LEO BB D V15 V35 M1.
In this case, it will open the .15 door first and wait for you to hit
<return> again before it opens the .35 door.
The complete list of item codes is:
C - Coffee, tea, hot chocolate
D - Donuts
B - Bagels
V - Vending machine
S - Snacks (munches, soup, etc.)
M - Money (stealing from the change box)
P - change your Password
T - show your Total changes for the month
I - Itemize your charges day-by-day.
The B, C, and D codes can be used without a number following, since their
prices are known to the machine. For the V, S, and M codes the amount
must be specified. You can also "uncharge" by specifying a negative
value. For example,
LEO -CCC M-25 V-35
credits your account for three cups of coffee, 25 cents in cash, and a
35 cent vending machine purchase, and arouses the suspicions of the
accounting program.
If you would like to know about charges for earlier months, use the "T" or
"I" command followed by ":" and 3 or more letters of the month name.
Thus if LEO were interested in totals for July and August and itemized
charges for September, he would type
LEO T:JUL T:AUG I:SEP
Bon appetit!
;
COMMENT Useful Sail macros;
REQUIRE "[]<>" DELIMITERS;
DEFINE TAB='11,LF='12,VT='13,FF='14,CR='15,ALT='175,DEL='177,↓=[(CR&LF)],
!=[COMMENT], THRU=[STEP 1 UNTIL], LN=[LENGTH],PROC=[SIMPLE PROCEDURE];
DEFINE PMAX=[300],CMAX=[12]; ! max people, charges/line;
DEFINE BILLFILE=[".PNY"],KEYFILE=["KEYWD"],DOORFILE=["DOORP"];
DEFINE TTYUUO=['051000000000], CALLI=['47000000000], VMICONO=['736600000000],
MTAPE=['072000000000];
DEFINE LH(WORD)=[((WORD)land '777777000000)],RH(WORD)=[((WORD)land '777777)];
DEFINE SYMBRK=2; ! allocate and initialize break tables;
DEFINE BREAK_TABLE(STUFF)=[
REDEFINE SYMBRK=SYMBRK+1, ZZZ=[BREAK]&CVS(SYMBRK);
IFCR SYMBRK>12 THENC REQUIRE "Too many break tables" MESSAGE; ENDC
SIMPLE PROCEDURE ZZZ; SETBREAK(SYMBRK,STUFF);
REQUIRE ZZZ INITIALIZATION;
];
DEFINE BREAK(ID,TERM,OMIT,MODES)= [
BREAK_TABLE(<TERM,OMIT,MODES>);
DEFINE ID=SYMBRK
];
DEFINE SCNBRK(ID,TERM,OMIT,MODES)= [
BREAK_TABLE(<TERM,OMIT,MODES>);
DEFINE ID(S)=[SCAN(S,]&CVS(SYMBRK)&[,BRK)]
];
DEFINE LETTERS=["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"];
DEFINE SAY(MES)=[OUT(TTY,MES)], INLINE=[INPUT(TTY,1)];
DEFINE TTY=[1],OUCH=[2]; ! TTY I/O channel, DSK CHAN.;
INTEGER INCH,BRK,EOF,FLAG,TTYEOF; ! INPUT/OUTPUT GLOBALS;
STRING BLANKS;
STRING PROC ASK(STRING MES); BEGIN SAY(MES); RETURN(INLINE) END;
STRING PROC RIGHT(INTEGER L; STRING S);
RETURN(IF LN(S)<L THEN BLANKS[1 TO L-LN(S)]&S ELSE S[∞-L+1 TO ∞]);
STRING PROC DEC2(INTEGER D); RETURN(("0"+D%10)&("0"+D MOD 10));
STRING PROC CENTS(INTEGER P); ! integer → $.cents;
RETURN(if P=0 then "0" else
(if abs P≥100 then cvs(P%100) else if P<0 then "-0" else null)&
"."&DEC2(abs P mod 100));
INTEGER PROC SLURP(STRING MES); BEGIN ! read a password;
integer pass;
! Turn off echo;
START_CODE; GETSTS TTY,1; TRO 1,'600; SETSTS TTY,(1); END;
pass←cvsix(right(6,ask(mes)));
! Turn on echo;
START_CODE; GETSTS TTY,1; TRZ 1,'600; SETSTS TTY,(1); END;
say(↓); return(pass)
end "SLURP";
INTEGER PROC HASH(INTEGER SIXB); RETURN(RH(SIXB*SIXB)); ! hash code;
PROC NOTICE(STRING MESS); BEGIN ! trouble in River City;
SAY("πππ"); SAY(MESS); SAY(" -- PLEASE NOTIFY FRONT OFFICE"&↓);
END;
PRELOAD_WITH "JAN","FEB","MAR","APR","MAY","JUN","JUL","AUG","SEP",
"OCT","NOV","DEC";
STRING ARRAY MONTH[1:12];
! Initialization;
PRELOAD_WITH CVSIX("GODMOD"),'15,0;
INTEGER ARRAY PNYPASS[0:3]; ! master password block;
INTEGER ARRAY DOOR[0:9]; ! vending machine charges;
INTEGER ARRAY PN[1:PMAX]; ! PN,,password;
STRING ARRAY FRIEND[1:PMAX]; ! friendly name;
boolean phantom; ! TRUE if this is a phantom;
INTEGER PTOP; ! last entry in PN;
SCNBRK(FLUSH,<" ,;$+">,NULL,"XNR"); ! FLUSH JUNK;
SCNBRK(SCALET,LETTERS,NULL,"XNR"); ! GOBBLE LETTERS;
SCNBRK(NUMS,"0123456789.",<" ,;$+">,"XNR"); ! gobble number;
SCNBRK(DIGS,"0123456789",NULL,"XNR"); ! gobble integer;
PROCEDURE TTYINIT; BEGIN ! INIT TTY;
OPEN(TTY,IF PHANTOM THEN "TTY4" ELSE "TTY",'401,1,1,400,BRK,TTYEOF);
START_CODE SETSTS TTY,1; END; ! Repair TTY IOS;
END;
PROCEDURE INITIAL; BEGIN ! INITIALIZE THE WORLD;
REQUIRE "PHONEY.SAI[1,LES]" SOURCE_FILE;
INTEGER II,pnpa;
LABEL FULL;
procedure store; if ptop<pmax then begin "store PN & friendly"
pn[ptop←ptop+1]←cvsix(prog);
friend[ptop]←friendly;
end
else begin notice("Too many users"); go to full end;
BLANKS←" ";
SETBREAK(1,LF,CR,"INS");
! Pick a TTY;
START_CODE SETOM II; TTYUUO 6,II; END;
PHANTOM←(II=-1); ! IF DETACHED, ITS A PHANTOM;
TTYINIT; ! INIT TTY;
! Open for accounting I/O;
open(ouch,"dsk",'17,0,0,400,brk,eof);
! Get PN list;
ptop←0;
while read do store;
while class do store;
! Read password file;
FULL: open(inch←getchan,"dsk",'10,4,0,400,brk,eof);
lookup(inch,keyfile,flag);
if flag then notice("Can't read "&keyfile) else
while pnpa←wordin(inch) do if rh(pnpa) then begin "search"
integer ps;
ps←lh(pnpa);
for ii←2 thru ptop do if ps=pn[ii] then begin
pn[ii]←pnpa; done
end;
end;
close(inch);
! Read VM prices;
lookup(inch,doorfile,flag);
if flag then notice("Can't read price file")
else arryin(inch,door[0],10);
release(inch);
end "INITIAL";
REQUIRE INITIAL INITIALIZATION;
PROCEDURE WRITEARR(STRING FILE;REFERENCE INTEGER A;INTEGER TOP); BEGIN
enter(ouch,file,flag);
if ¬flag then arryout(ouch,A,top) else
notice(file&" can't be written");
close(ouch);
END;
! SCANNING PROCEDURES;
while true do begin "main"
LABEL START,FOUND;
string s,so; ! working string, original;
STRING VMQ,tiq,mon; ! VM queue, TI queue, month;
INTEGER DATE,TIME,DAY; ! current date, time, day;
INTEGER CI,NEG; ! # of charges, minus detected;
INTEGER ARRAY RECORD[1:128+CMAX*2]; ! account buffer;
INTEGER ARRAY CHARGE[0:CMAX*2]; ! PN,,day & code,,price;
INTEGER ID,PNI,TOT; ! Prog. ID, index, total bill;
PROC BARF(STRING MES); BEGIN ! another try;
say("πππ"); say(mes&↓); go to start
end;
PROC ERROR; BARF(so[1 to ∞-ln(s)]&"? Please retype the line");
BOOLEAN PROCEDURE MASTER(INTEGER SIXB); BEGIN ! test master password;
BOOLEAN MF;
PNYPASS[3]←SIXB;
LOOKUP(OUCH,"PNYSYS.UFD[1,1]",MF);
IF MF THEN BEGIN CLOSE(OUCH); BARF("No [PNY,SYS] UFD"); END;
START_CODE SETOM MF; MTAPE OUCH,PNYPASS[0]; SETZM MF END;
! MTAPE skips if SIXB is the password;
CLOSE(OUCH); RETURN(MF)
END "MASTER";
INTEGER PROC SCENT; BEGIN ! convert price;
integer i,j,k;
string ss;
if s="-" then neg←lop(s);
i←cvd(digs(<ss←nums(s)>));
if brk="." then begin "decimals"
j←lop(ss);
if ln(ss)=0 then i←100*i else
if "0"≤(j←lop(ss))≤"9" ∧ "0"≤(k←lop(ss))≤"9" then
i←100*i+10*j+k-(10*"0"+"0") else error;
end;
if ln(ss) then error;
return(if neg then -i else i)
end "SCENT";
PROC BILL(INTEGER CODE,AMT); BEGIN ! write the bill;
if ci≥cmax then barf("Line too long");
charge[ci]←ID lor day;
charge[ci+1]←cvsix(code) lor rh(amt);
ci←ci+2; tot←tot+amt;
end;
PROC CHECK(INTEGER CODE,PRICE); BEGIN ! see if its a multiple of base price;
integer pi;
if (pi←scent)=0 ∨ (pi mod price)≠0 then
barf(""""&code&""" price must be a multiple of "¢s(price));
BILL(CODE,pi);
END;
PROC POOT(INTEGER CODE,PRICE); BEGIN
integer pi,pj;
if s= code then begin "string"
pi←price;
do begin
pi←pi+price; pj←lop(s); flush(s);
end
until s≠code;
if neg then pi←-pi;
end "string"
else begin "price"
string ps;
pj←neg; ps←s; ! save the word;
if (pi←scent)=0 then begin "nonum"
pi←if (neg←pj) then -price else price;
s←ps; ! restore it;
end
else if (pi mod price)≠0 then
barf(""""&code&""" price must be a multiple of "&
cents(price));
end "price";
bill(code,pi);
END "POOT";
PROC VENDI; BEGIN
integer va,vi,vj;
if va←abs(vi←scent) then
for vj←0 thru 9 do if va=door[vj] then begin "gotcha"
if vi>0 then vmq←vmq&vj; ! put it on the VM queue;
bill("V",vi); ! & bill it;
return
end;
barf("The vending machine doesn't have a "¢s(va)&" door");
END;
INTEGER PROC TOTE; ! this month, or earlier? ;
if s≠":" then return((date div 31)mod 12 + 1) else begin "earlier"
integer ti; string ts;
ti←lop(s); flush(s); ! get to month name;
ts←scalet(s); ts←ts[1 to 3]; ! take first 3 letters;
for ti←1 thru 12 do if equ(ts,month[ti]) then return(ti);
barf(ts&" isn't a month");
end;
! OUTPUT PROCEDURES;
PROCEDURE FIXPASS; WHILE TRUE DO BEGIN ! NULLIFY PASSWORDS;
label more;
integer fpn,fi;
MORE:
if (fpn←cvsix(ask("PN=")))=0 then begin
writearr(keyfile,pn[1],ptop); return
end;
for fi←1 step 1 until ptop do if fpn=lh(pn[fi]) then
begin pn[fi]←fpn; go to more end;
say("No such guy"&↓);
END;
PROCEDURE NEWPRICE; BEGIN ! change VM prices;
integer ni;
say("BAR PRICE"&↓); ! first, print the old prices;
for ni←0 thru 9 do say(<cvs(ni)&right(5,cents(door[ni]))&↓>);
while ln(s←ask("*")) do if 0≤(ni←LOP(s)-"0")≤9 then begin "examine"
if ln(s) then begin
if s≠" " then begin say("?"&↓); continue end;
flush(s); neg←0; door[ni]←scent;
end
else say(<right(6,cents(door[ni]))&↓>);
end
else say("?"&↓);
writearr(doorfile,door[0],10);
END;
PROCEDURE SAVIT; BEGIN ! update charge file;
integer array data[0:5];
integer bn,si; ! block #, word #;
lookup(ouch,mon&billfile,flag);
if flag then begin bn←1; si←0; end else begin "read file"
fileinfo(data);
si ← -(data[3] rot 18); bn ← si%128 + 1;
si←si mod 128;
if si then begin
useti(ouch,bn); arryin(ouch,record[1],si)
end;
end;
arrblt(record[si+1],charge[0],ci); ! add the new entries;
enter(ouch,mon&billfile,flag);
if flag then notice("Cannot write "&mon&billfile);
useto(ouch,bn);
arryout(ouch,record[1],si+ci);
close(ouch);
END "SAVIT";
PROC VENDOUT; BEGIN ! OPEN VM DOORS;
integer vi,vd;
vi←((vd←lop(vmq)) lsh 4) xor '370;
start_code calli '400005; vmicono @vi; calli '400006 end;
if ln(vmq) then ask(cents(door[vd])&" door open. Hit return for next one");
END;
PROCEDURE TOTAL; BEGIN
DEFINE CODES=["VCDBSM"];
INTEGER ARRAY SUBTOT[1:LN(CODES)];
integer w1,w2,cod,val,ti,dayn;
BOOLEAN itemize;
string ts,mont;
itemize←(ti←lop(tiq))land '40; ! itemize≠0 means itemize;
open(inch←getchan,"dsk",'10,4,0,400,brk,eof);
lookup(inch,(mont←month[ti land '37])&billfile,flag);
if flag then begin say(↓&"No data for "&mont&↓); return end;
if itemize then begin dayn←0; say(↓&"DATE CHARGES FOR "&mont); end;
while w1←wordin(inch) do begin "SUM" ! PN,,day;
label more;
w2←wordin(inch); ! code,,value;
if lh(w1)=ID then begin "his own"
if itemize ∧ (val←rh(w1))≠dayn then
say(↓&right(2,cvs(dayn←val)));
cod←(w2 lsh-30)+'40; ! ASCII code;
if (val←rh(w2))land '400000 then
val←val lor '777777000000; ! integer value (+-);
for ti←1 thru ln(codes) do if cod=codes[ti for 1]
then begin
subtot[ti]←subtot[ti]+val;
if itemize then say(" "&cod&cvs(val));
go to more
end;
notice("Garbage in the accounting files");
end "his own";
MORE: end "SUM";
release(inch);
val←0; ts←null;
for ti←1 thru ln(codes) do if w1←subtot[ti] then begin "subtotals"
val←val+w1;
ts←ts&" "&codes[ti for 1]¢s(w1);
end;
say(↓&"Total for "&mont&": $"¢s(val)&" = "&ts&↓);
END "TOTAL";
! DO IT;
BOOLEAN NEWPASSP,newpricep; ! new password, total requested;
integer mc,key;
PROCEDURE NIX(BOOLEAN BAZ); IF BAZ THEN SAY(<"πππSORRY, CHARLIE"&↓>) ELSE
BARF("FOO, YOU ARE A PASSWORD HACKER!");
START:
VMQ←TIQ←NULL; ! clear for new entry;
CI←NEWPASSP←NEWPRICEP←tot←0;
do begin "read"
flush(<S←ask(↓&"EAT! ")>); ! get PN & command;
if ttyeof then begin release(tty); ttyinit end; ! fix end-of-file;
end
until ln(s);
time←call(0,"timer")%3600; ! time since midnight in mins.;
day←(date←call(0,"date"))mod 31 +1; ! date in system format;
mon←month[(date div 31)mod 12 +1];
if(id←cvsix(scalet(s)))=cvsix("SYS") then begin "master mode"
flush(s); so←s;
while mc←lop(s) do begin
if mc="P" then newpassp←true else
if mc="V" then newpricep←true else error;
flush(s);
end;
while ¬master(mc←slurp("PASSWORD=")) do NIX(MC);
if newpassp then fixpass; ! nullify passords;
if newpricep then newprice;
go to start
end;
for pni←1 thru ptop do if id=lh(key←pn[pni]) then go to found;
barf("Sorry, I don't know you");
FOUND:
flush(s); so←s;
while ln(s) do begin "decode"
if neg←(mc←lop(s))="-" then begin flush(s); mc←lop(s) end;
flush(s);
if mc="P" then newpassp←true else
if mc="B" then poot("B",15) else
if mc="C" then poot("C",10) else
if mc="D" then poot("D",20) else
if mc="V" then vendi else
if mc="S" then check("S",5) else
if mc="M" then check("M",1) else
if mc="T" then tiq←tiq&tote else
if mc="I" then tiq←tiq&(tote lor '40) else error;
flush(s);
end "decode";
if ¬(key←rh(key)) then say("PASSWORD="&↓) else
while hash(mc←slurp("PASSWORD="))≠key do nix(mc);
if newpassp then begin "newpass" ! enter new password;
pn[pni]←lh(pn[pni]) lor hash(slurp("NEW PASSWORD="));
WRITEARR(keyfile,pn[1],ptop); ! write out password file;
END;
if ci then savit; ! write billing file;
if phantom then while ln(vmq) do vendout; ! activate VM;
if ci then say(<friend[pni]&" ate "¢s(tot)&" on "&cvs(date%(31*12)+1964)&
" "&mon&" "&cvs(day)&" "&cvs(time%60)&":"&dec2(time mod 60)&↓>);
while ln(tiq) do total; ! show itemization or totals for month;
end "main"
end "PONY"